/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package it.polimi.meteocal_pappalardo_sacchi.bean;

import it.polimi.meteocal_pappalardo_sacchi.entity.Event;
import it.polimi.meteocal_pappalardo_sacchi.entity.WeatherCondition;
import it.polimi.meteocal_pappalardo_sacchi.entity.WeatherForecast;
import java.io.IOException;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import net.aksingh.java.api.owm.DailyForecastData;
import net.aksingh.java.api.owm.DailyForecastData.Forecast;
import net.aksingh.java.api.owm.OpenWeatherMap;
import org.json.JSONException;


/**
 *
 * @author Elena
 */
@Named
@RequestScoped
public class ForecastBean implements Serializable {

    @Inject
    ForecastManager forecastManager;
    @Inject
    WeatherManager weatherManager;

    private WeatherForecast weatherForecast;
    private List<WeatherForecast> weatherForecastList;
    protected OpenWeatherMap owm;
    private static final byte MAX_DAYS_TO_FORECAST = 15;

    public ForecastBean() {
    }

    @PostConstruct
    private void init() {
        owm = new OpenWeatherMap(OpenWeatherMap.OWM_URL.PARAMETER_UNITS_VALUE_METRIC, "778039ef9b1b2515f3e9f4d7d7207386");
        weatherForecast = new WeatherForecast();
        weatherForecastList = new ArrayList<WeatherForecast>();
    }

    public WeatherForecast getWeatherForecast() {
        return weatherForecast;
    }

    public void setWeatherForecast(WeatherForecast weatherForecast) {
        this.weatherForecast = weatherForecast;
    }

    public List<WeatherForecast> getWeatherForecastList() {
        return weatherForecastList;
    }

    public void setWeatherForecastList(List<WeatherForecast> weatherForecastList) {
        this.weatherForecastList = weatherForecastList;
    }

    /**
     * @param event the event with an unfavorable forecast for which I shall retrieve a suggestion
     * @return null if the application cannot connect to openweathermap
     * java.sql.Date suggestion: the new starting date suggested
     * java.sql.Date event.startingDate: the old starting date in case of no good dates are found
     */
    public java.sql.Date retrieveSuggestion(Event event) {

        DailyForecastData dailyForecast;
        try {
            Period duration = Period.between(event.getStartingDate().toLocalDate(), event.getEndingDate().toLocalDate());
            int eventDuration = duration.getDays() + 1;
            dailyForecast = owm.dailyForecastByCityName(event.getCity(), MAX_DAYS_TO_FORECAST);
            
            int match = 0;
            java.sql.Date suggestion = null;
            //if the event is longer than one month, I skip the control directly
            if(duration.getMonths()==0){
                for (Forecast df : dailyForecast.getForecast_List()) {
                    if (!df.getDateTime().before(event.getStartingDate())) {
                        compose(df);
                        if (favorableForecast(getWeatherForecast().getWeatherCondition(), event.getWeatherCondition())) {
                            if (match == 0) {
                                suggestion = getWeatherForecast().getStartingDate();
                            }
                            match++;
                        } else {
                            match = 0;
                        }
                        //I return the starting date corresponding to the closer favorable period of time
                        if (match == eventDuration) {
                            return suggestion;
                        }
                    }
                }
            }
            return event.getStartingDate();
        } catch (IOException ex) {
            Logger.getLogger(WeatherManager.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        } catch (JSONException ex) {
            Logger.getLogger(WeatherManager.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    public boolean retrieveForecastForEvent(Event event) {
        if (retrieveMultipleForecast(event.getCity(), event.getStartingDate(), event.getEndingDate())) {
            event.setWeatherForecastList(getWeatherForecastList());
            return true;
        }
        return false;
    }

    public boolean retrieveMultipleForecast(String cityName, java.sql.Date startingDate, java.sql.Date endingDate) {

        DailyForecastData dailyForecast;
        byte daysUntilStartingDate = daysUntilDate(startingDate);
        byte daysUntilEndingDate = daysUntilDate(endingDate);

        if (daysUntilStartingDate > MAX_DAYS_TO_FORECAST) {
            return false;
        }

        if (daysUntilEndingDate > MAX_DAYS_TO_FORECAST) {
            daysUntilEndingDate = MAX_DAYS_TO_FORECAST;
        }

        try {
            dailyForecast = owm.dailyForecastByCityName(cityName, daysUntilEndingDate);
            for (Forecast df : dailyForecast.getForecast_List()) {
                if (!df.getDateTime().before(startingDate)) {
                    compose(df);
                    getWeatherForecastList().add(getWeatherForecast());
                    setWeatherForecast(new WeatherForecast());
                }
            }
            Collections.sort(weatherForecastList);
            return true;
        } catch (IOException ex) {
            Logger.getLogger(WeatherManager.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        } catch (JSONException ex) {
            Logger.getLogger(WeatherManager.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    /**
     * 
     * @param date event date
     * @return the number of days from now to date
     *          15 if the days are more than MAX_DAYS_TO_FORECAST
     */
    protected Byte daysUntilDate(java.sql.Date date) {
        ZoneId zone = ZoneId.of("Europe/Paris");
        LocalDate now = LocalDate.now(zone);
        LocalDate eventDate = date.toLocalDate();
        int period = (int) ChronoUnit.DAYS.between(now, eventDate);
        byte days = (byte) (period + 1);
        return (byte) days;
    }

    private void compose(Forecast forecast) {

        if (forecast.hasDateTime()) {
            getWeatherForecast().setStartingDate(new java.sql.Date(forecast.getDateTime().getTime()));
            getWeatherForecast().setStartingTime(new java.sql.Time(forecast.getDateTime().getTime()));
        }

        WeatherCondition wc = weatherManager.createFromForecast(forecast);
        getWeatherForecast().setWeatherCondition(wc);
    }

    protected boolean favorableForecast(WeatherCondition forecastCondition, WeatherCondition eventCondition) {
        if (weatherManager.checkBadCondition(eventCondition, forecastCondition))
            return false;
        else
            return true;
    }
}
